#ifndef _MATRIX_
#define _MATRIX_

#include "vec.h"
#include "quat.h"

struct	Mat4
{
	float	_11, _12, _13, _14;
	float	_21, _22, _23, _24;
	float	_31, _32, _33, _34;
	float	_41, _42, _43, _44;

	Mat4 Inverse() const;
	Vec3 Transform(const Vec3 & p) const;
	Vec4 Transform(const Vec4 & p) const;
	Vec3 TransformNormal(const Vec3 & p) const;
	Vec3 TransformHomogen(const Vec3& p) const;
	Mat4 Transpose() const;

	const Vec3& GetRow0() const { return *(Vec3*)&_11; }
	const Vec3& GetRow1() const { return *(Vec3*)&_21; }
	const Vec3& GetRow2() const { return *(Vec3*)&_31; }

	Vec3&	Position() { return *(Vec3*)&_41; }
	const Vec3 & Position() const { return *(Vec3*)&_41; }

	Vec3	GetScale() const { return Vec3(_11, _22, _33); }

	void	GetRotation(Quat4& q) const;
	Quat4	GetRotation() const { Quat4 q; GetRotation(q); return q; }

	void	SetTranslate(const Vec3 & p);
	void	Decompose(Vec3& translate, Quat4& rotate, Vec3& scale) const;
	void	Decompose(Vec3& translate, Vec3& rotate, Vec3& scale) const;

	static Mat4 Make(const Quat4& rot, const Vec3& tanslate);
	static Mat4 Make(const Vec3& xaxis, const Vec3& yaxis, const Vec3& zxaxis, const Vec3& tanslate);
	static Mat4 Make(const Vec3& axis, float rad);

	static Mat4 Multiply(const Mat4 & a, const Mat4 & b);
	static Mat4 Inverse(const Mat4 & m) { return m.Inverse(); }

	static Mat4 LookAt(const Vec3 & pivot, const Vec3 & target, const Vec3 & upvec);
	static Mat4 LookAt(const Vec3 & pivot, const Vec3 & target);
	static Mat4 Projection(float nearwidth, float nearheight, float nearclip, float farclip);
	static Mat4 ProjectionWithFov(float fov, float nearclip, float farclip, float aspectratio);
	static Mat4 ProjectionOffCenter(float l, float r, float b, float t, float nearclip, float farclip);
	static Mat4 InfiniteProjectionWithFov(float fov, float nearclip, float aspectratio, float epsilon=2.4f*10e-7f);
	static Mat4 Ortho(float w, float h, float nearclip, float farclip);
	static Mat4 OrthoOffCenter(float l, float r, float b, float t, float nearclip, float farclip); 
	static Mat4 RotationX(float rad);
	static Mat4 RotationY(float rad);
	static Mat4 RotationZ(float rad);
	static Mat4 Rotation(const Quat4& q);
	static Mat4 Scaling(const Vec3& s);
	static Mat4 Scaling(float s);
	static Mat4 Translate(const Vec3 & p);

	//static Mat4 Mirror(const Vec3& normal, const Point3& point);
	static Mat4 IDENTITY;

	// OpenGL
#ifdef _OPENGLES
	static Mat4 ProjectionGL(float nearwidth, float nearheight, float nearclip, float farclip);
	static Mat4 ProjectionWithFovGL(float fov, float nearclip, float farclip, float aspectratio);
	static Mat4 ProjectionOffCenterGL(float l, float r, float b, float t, float nearclip, float farclip);
	static Mat4 InfiniteProjectionWithFovGL(float fov, float nearclip, float aspectratio, float epsilon=2.4f*10e-7f);
	static Mat4 OrthoGL(float w, float h, float nearclip, float farclip);
	static Mat4 OrthoOffCenterGL(float minX, float maxX, float minY, float maxY, float minZ, float maxZ); 
#endif

} ;


inline Vec3 operator * (const Vec3 & p, const Mat4 & tm) { return tm.Transform(p); }
inline Vec4 operator * (const Vec4 & p, const Mat4 & tm) { return tm.Transform(p); }
inline Mat4 operator * (const Mat4 & m1, const Mat4 & m2) { return Mat4::Multiply(m1, m2); }

struct	Mat3
{
	float	_11, _12, _13;
	float	_21, _22, _23;
	float	_31, _32, _33;

	void FromMat4(const Mat4& m);
	Mat4 ToMat4() const;
	float Determinant()
	{
		return  _11 * _22 * _33 + _12 * _23* _31 + _13 * _21 * _32 - _13 * _22 * _31 - _12 * _21 * _33 - _11 * _23 * _32;
	}

	static Mat3 IDENTITY;
};

#endif